/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.community.component.skin;

import cn.ac.nercis.pes.community.component.controls.PesSwitch;
import cn.ac.nercis.pes.community.utils.IconUtils;
import javafx.animation.Animation;
import javafx.animation.TranslateTransition;
import javafx.beans.property.DoubleProperty;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableDoubleProperty;
import javafx.css.StyleableProperty;
import javafx.css.converter.SizeConverter;
import javafx.geometry.Pos;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Basic Skin implementation for the {@link PesSwitch}
 * @author zpy
 * @version 1.0.0
 */
public class PesSwitchSkin extends SkinBase<PesSwitch> {

    private final StackPane thumb;
    private final StackPane thumbArea;
    private final Label label;
    private final Label thumbText;
    private final StackPane labelContainer;
    private final TranslateTransition transition;

    public PesSwitchSkin(PesSwitch control) {
        super(control);
        thumb = new StackPane();
        thumbArea = new StackPane();
        thumbText = new Label();
        label = new Label();
        labelContainer = new StackPane();
        transition = new TranslateTransition(Duration.millis(getThumbMoveAnimationTime()), thumb);
        transition.setFromX(0.0);

        thumbText.setGraphic(IconUtils.getGlyph("switch-icon","switch-icon"));
        thumbText.setContentDisplay(ContentDisplay.RIGHT);
        thumbText.setText(control.isSelected()?"开启":"关闭");
        label.textProperty().bind(control.textProperty());
        getChildren().addAll(labelContainer, thumbArea, thumb);
        labelContainer.getChildren().addAll(label);
        StackPane.setAlignment(label, Pos.CENTER_LEFT);


        thumb.getChildren().add(thumbText);
        StackPane.setAlignment(thumbText, Pos.CENTER);
        thumb.getStyleClass().setAll("thumb");
        thumbArea.getStyleClass().setAll("thumb-area");

        control.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
            if (event.getCode() == KeyCode.SPACE) {
                toggle(control);
            }
        });
        thumbArea.setOnMouseReleased(event -> toggle(control));
        thumb.setOnMouseReleased(event -> toggle(control));
        control.selectedProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue.booleanValue() != oldValue.booleanValue()){
                selectedStateChanged();
            }
        });
    }

    private void selectedStateChanged() {
        // Stop the transition if it was already running, has no effect otherwise.
        transition.stop();
        if (getSkinnable().isSelected()) {
            thumbText.setText("开启");
            transition.setRate(1.0);
            transition.jumpTo(Duration.ZERO);
        } else {
            thumbText.setText("关闭");
            // If we are not selected, we need to go from right to left.
            transition.setRate(-1.0);
            transition.jumpTo(transition.getDuration());
        }
        transition.play();
    }

    private void toggle(PesSwitch toggleSwitch) {
        toggleSwitch.setSelected(!toggleSwitch.isSelected());
    }

    private DoubleProperty thumbMoveAnimationTime;

    private DoubleProperty thumbMoveAnimationTimeProperty() {
        if (thumbMoveAnimationTime == null) {
            thumbMoveAnimationTime = new StyleableDoubleProperty(200) {

                @Override
                protected void invalidated() {
                    double newValue = get();
                    if (newValue <= 0) {
                        newValue = 1; // set min duration as 1 for transition to play
                    }
                    transition.setDuration(Duration.millis(newValue));
                }

                @Override
                public Object getBean() {
                    return PesSwitchSkin.this;
                }

                @Override
                public String getName() {
                    return "thumbMoveAnimationTime";
                }

                @Override
                public CssMetaData<PesSwitch,Number> getCssMetaData() {
                    return THUMB_MOVE_ANIMATION_TIME;
                }
            };
        }
        return thumbMoveAnimationTime;
    }

    private double getThumbMoveAnimationTime() {
        return thumbMoveAnimationTime == null ? 200 : thumbMoveAnimationTime.get();
    }

    @Override
    protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
        PesSwitch toggleSwitch = getSkinnable();
        double thumbWidth = snapSizeX(thumb.prefWidth(-1));
        double thumbHeight = snapSizeY(thumb.prefHeight(-1));
        thumb.resize(thumbWidth, thumbHeight);

        double thumbAreaWidth = snapSizeX(thumbArea.prefWidth(-1));
        double thumbAreaHeight = snapSizeY(thumbArea.prefHeight(-1));
        double thumbAreaY = snapPositionY(contentY + (contentHeight / 2) - (thumbAreaHeight / 2));

        thumbArea.resize(thumbAreaWidth, thumbAreaHeight);
        thumbArea.setLayoutX(contentWidth - thumbAreaWidth);
        thumbArea.setLayoutY(thumbAreaY);

        labelContainer.resize(contentWidth - thumbAreaWidth, thumbAreaHeight);
        labelContainer.setLayoutY(thumbAreaY);

        // Layout the thumb on the "unselected" position
        thumb.setLayoutX(thumbArea.getLayoutX());
        thumb.setLayoutY(thumbAreaY + (thumbAreaHeight - thumbHeight) / 2);

        // Each time the layout is done, recompute the thumb "selected" position and apply it to the transition target.
        final double thumbTarget = thumbAreaWidth - thumbWidth;
        transition.setToX(thumbTarget);

        if (transition.getStatus() == Animation.Status.RUNNING) {
            // If the transition is running, it must be restarted for the value to be properly updated.
            final Duration currentTime = transition.getCurrentTime();
            transition.stop();
            transition.playFrom(currentTime);
        } else {
            // If the transition is not running, simply apply the translate value.
            thumb.setTranslateX(toggleSwitch.isSelected() ? thumbTarget : 0.0);
        }
    }

    @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
        return leftInset + label.prefWidth(-1) + thumbArea.prefWidth(-1) + rightInset;
    }

    @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
        return topInset + Math.max(thumb.prefHeight(-1), label.prefHeight(-1)) + bottomInset;
    }

    @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
        return leftInset + label.prefWidth(-1) + 20 + thumbArea.prefWidth(-1) + rightInset;
    }

    @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
        return topInset + Math.max(thumb.prefHeight(-1), label.prefHeight(-1)) + bottomInset;
    }

    @Override
    protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
        return getSkinnable().prefWidth(height);
    }

    @Override
    protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
        return getSkinnable().prefHeight(width);
    }


    private static final CssMetaData<PesSwitch, Number> THUMB_MOVE_ANIMATION_TIME =
            new CssMetaData<>("-thumb-move-animation-time",
                    SizeConverter.getInstance(), 200) {

                @Override
                public boolean isSettable(PesSwitch toggleSwitch) {
                    final PesSwitchSkin skin = (PesSwitchSkin) toggleSwitch.getSkin();
                    return skin.thumbMoveAnimationTime == null ||
                            !skin.thumbMoveAnimationTime.isBound();
                }

                @Override
                public StyleableProperty<Number> getStyleableProperty(PesSwitch toggleSwitch) {
                    final PesSwitchSkin skin = (PesSwitchSkin) toggleSwitch.getSkin();
                    return (StyleableProperty<Number>) skin.thumbMoveAnimationTimeProperty();
                }
            };

    private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;

    static {
        final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(SkinBase.getClassCssMetaData());
        styleables.add(THUMB_MOVE_ANIMATION_TIME);
        STYLEABLES = Collections.unmodifiableList(styleables);
    }

    /**
     * @return The CssMetaData associated with this class, which may include the
     * CssMetaData of its super classes.
     */
    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
        return STYLEABLES;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
        return getClassCssMetaData();
    }

}